package com.xlongwei.todays.controller;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.noear.snack.ONode;
import org.noear.solon.Solon;
import org.noear.solon.Utils;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Param;
import org.noear.solon.annotation.Post;
import org.noear.solon.core.handle.Result;
import org.noear.solon.data.cache.CacheService;
import org.noear.solon.data.cache.LocalCacheService;
import org.noear.solon.validation.annotation.NotBlank;
import org.noear.solon.validation.annotation.Pattern;
import org.noear.solon.validation.annotation.Valid;
import org.slf4j.LoggerFactory;
import com.xlongwei.todays.config.AppConfig;
import com.xlongwei.todays.config.MySqlUtils;
import com.xlongwei.todays.dao.UserDao;
import com.xlongwei.todays.model.User;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;

@Valid
@Slf4j
@Controller
@Mapping("user")
public class UserController {
    public static final String PATTERN_PASS = "^(\\S{1,6})||(\\S{64})$";
    public static final String PATTERN_NAME = "^\\S{1,7}$";
    public static final String PATTERN_MOBILE = "^1\\d{10}$";
    @Inject
    UserDao userDao;
    @Inject
    MySqlUtils sqlUtils;
    @Inject
    CacheService cacheService;

    @Post
    @Mapping("login")
    public Result<?> login(@NotBlank @Pattern(PATTERN_MOBILE) String mobile,
            @NotBlank @Pattern(PATTERN_PASS) String pass) {
        if (StpUtil.isLogin()) {
            StpUtil.logout(StpUtil.getLoginId());
        }
        Long mobileL = Long.parseLong(mobile);
        User user = userDao.findByMobile(mobileL);
        if ((user != null && (user.getLevel() <= 0 || !validPass(pass, user)))
                || (user == null && pass.length() == 64)) {
            return Result.failure("登录失败");
        }
        boolean newUser = user == null;
        if (newUser) {
            user = new User();
            user.setMobile(mobileL);
            user.setPass(pass);
            user.setLevel(2);
            Integer userid = userDao.create(user);
            user.setUserid(userid);
        }
        String id = "" + user.getUserid();
        StpUtil.login(id);
        return Result.succeed(user);
    }

    private boolean validPass(String pass, User user) {
        if (pass.length() == 64) {// sha256
            String hash = user.getPass();
            hash = SaSecureUtil.sha256(hash) + hash;
            LocalDate dt = LocalDate.now();
            int rounds = rounds(dt); // 根据月+日+周变化（与前端一致），每天使用不同hash值登录
            for (int i = 0; i < rounds; i++) {
                hash = (dt.getMonthValue() - 1) + SaSecureUtil.sha256(hash) + dt.getDayOfMonth();
            }
            hash = SaSecureUtil.sha256(hash);
            return Objects.equals(pass, hash);
        } else {
            return Objects.equals(pass, user.getPass());
        }
    }

    public static int rounds(LocalDate dt) {
        int m = dt.getMonthValue(), d = dt.getDayOfMonth(), w = dt.getDayOfWeek().getValue();
        return 1 + (((m - 1) + d + (w == 7 ? 0 : w)) % 13) % 7;
    }

    @Post
    @Mapping("checkLogin")
    public Result<?> checkLogin(@Pattern(PATTERN_MOBILE) String mobile) {
        Long mobileL = Long.parseLong(mobile);
        User user = userDao.findByMobile(mobileL);
        ONode node = new ONode();
        node.set("tenants", Collections.emptyList());
        node.set("newUser", user == null);
        node.set("friends", Solon.cfg().getBool("friendship.enable", true) ? Solon.cfg().toBean("friendship.links", ArrayList.class) : Collections.emptyMap());
        return Result.succeed(node);
    }

    @Post
    @SaCheckLogin
    @Mapping("changePass")
    public Result<?> changePass(@NotBlank String pass,
            @NotBlank @Pattern(PATTERN_PASS) String newPass) {
        User user = null;
        boolean update = false;
        if (pass.matches("\\d+") && isAdmin()) {
            user = userDao.retrieve(Integer.valueOf(pass));
            update = user != null;
        }
        if (update == false) {
            user = userDao.retrieve(userid());
            update = user != null && Objects.equals(pass, user.getPass());
        }
        if (update) {
            user.setPass(newPass);
            userDao.update(user);
            return Result.succeed(null, "改密成功");
        }
        return Result.failure("改密失败");
    }

    @Get
    @Post
    @Mapping("list")
    @SaCheckRole(value = "admin")
    public Result<?> list() {
        List<User> list = userDao.list();
        return Result.succeed(list);
    }

    @Post
    @Mapping("clearCache")
    @SaCheckRole(value = "admin")
    public Result<?> clearCache() {
        if (AppConfig.enableCache) {
            if (cacheService instanceof LocalCacheService) {
                ((LocalCacheService) cacheService).clear();
            }
            return Result.succeed();
        }
        return Result.failure();
    }

    @Post
    @Mapping("admin")
    @SaCheckRole(value = "admin")
    public Result<?> admin(String sql) {
        Object obj = sqlUtils.sql(sql).queryRowList(Map.class);
        return Result.succeed(obj);
    }

    @Post
    @SaCheckLogin
    @Mapping("logout")
    public Result<?> logout() {
        StpUtil.logout(StpUtil.getLoginId());
        return Result.succeed(null, "登出成功");
    }

    @Post
    @Mapping("log")
    public Object log(@Param("logger") String loggerName, @Param("level") String levelName,
            @Param("token") String tokenValue) {
        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        List<ch.qos.logback.classic.Logger> loggers = null;
        if (!Utils.isBlank(loggerName)) {
            ch.qos.logback.classic.Logger logger = lc.getLogger(loggerName);
            if (logger != null) {
                loggers = Arrays.asList(logger);
                String token = System.getProperty("logserver.token");
                if (!Utils.isBlank(levelName)
                        && (Utils.isBlank(token) || token.equals(tokenValue))) {
                    Level level = Level.toLevel(levelName, null);
                    log.warn("change logger:{} level from:{} to:{}", logger.getName(),
                            logger.getLevel(), level);
                    logger.setLevel(level);
                }
            }
        }
        if (loggers == null) {
            loggers = lc.getLoggerList();
        }
        log.info("check logger level, loggers:{}", loggers.size());
        List<Map<String, String>> list = loggers.stream()
                .sorted((a, b) -> a.getName().compareTo(b.getName())).map(logger -> {
                    Map<String, String> m = new HashMap<>();
                    m.put("logger", logger.getName());
                    m.put("level", Objects.toString(logger.getLevel(), ""));
                    return m;
                }).collect(Collectors.toList());
        return ONode.load(Collections.singletonMap("loggers", list));
    }

    public static Integer userid() {
        String id = StpUtil.getLoginId().toString();
        int idx = id.indexOf('@');
        return Integer.parseInt(idx == -1 ? id : id.substring(0, idx));
    }

    public static boolean isAdmin() {
        return 1 == userid();
    }
}
